package org.example.seckill.service.impl;

import cn.hutool.core.lang.UUID;
import org.example.redis.service.RedisService;
import org.example.seckill.api.CouponService;
import org.example.seckill.api.ProductService;
import org.example.seckill.api.module.SkuInfo;
import org.example.seckill.api.module.SkuSeckill;
import org.example.seckill.constant.SeckillRedisConstants;
import org.example.seckill.entity.RegularSeckill;
import org.example.seckill.service.SeckillScheduledService;
import org.redisson.api.RLock;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
public class SeckillScheduledServiceImpl implements SeckillScheduledService {

    @Autowired
    private ProductService productService;

    @Autowired
    private CouponService couponService;

    @Autowired
    private RedisService redisService;

    @Autowired
    private RedissonClient redissonClient;

    private final static String DATE_FORMAT = "yyyyMMdd";

    private final static DateTimeFormatter targetFormatter = DateTimeFormatter.ofPattern(DATE_FORMAT);

    private final static String SECKILL_LOCK_STOCK = "seckill:lock:stock";

    private final static String SECKILL_SKU_SEMAPHORE = "seckill:semaphore:";

    private final static String SECKILL_MARK_KEY = "seckill:mark:";

    private final static String SECKILL_MARK_VALUE = "1";


    public void upRegularSeckill() throws InterruptedException {
        // 获取锁
        LocalDate localDate = LocalDate.now().plusDays(1L);
        RLock lock = redissonClient.getLock(SECKILL_LOCK_STOCK);
        boolean tryLock = lock.tryLock(10L, TimeUnit.SECONDS);
        if (tryLock){
            try {
                boolean exist = redisService.isKeyExist(SECKILL_MARK_KEY + targetFormatter.format(localDate));
                if (exist) return;
                // 获取到秒杀的商品
                List<SkuSeckill> skuSeckills = couponService.regularSeckill().getData();
                if (!skuSeckills.isEmpty()) {
                    // 收集到所有sku_id获取sku信息
                    List<Long> collect = skuSeckills.stream().map(SkuSeckill::getSkuId).collect(Collectors.toList());
                    Map<Long, SkuInfo> skuInfoMap = productService.getSkuListByIds(collect).getData();
                    for (SkuSeckill seckill : skuSeckills) {
                        RegularSeckill regularSeckill = new RegularSeckill();
                        BeanUtils.copyProperties(seckill, regularSeckill);
                        BeanUtils.copyProperties(skuInfoMap.get(seckill.getSkuId()), regularSeckill);
                        String key = SeckillRedisConstants.REGULAR_SECKILL + targetFormatter.format(localDate) + ":" + seckill.getPromotionId() + ":" + seckill.getStartTime() + "_" + seckill.getEndTime();
                        // 随机码
                        String randomCode = UUID.randomUUID().toString(true);
                        regularSeckill.setRandomCode(randomCode);
                        // 设置信号量
                        RSemaphore semaphore = redissonClient.getSemaphore(SECKILL_SKU_SEMAPHORE + randomCode);
                        semaphore.trySetPermits(seckill.getSeckillCount());
                        // 保存
                        redisService.setHash(key, String.valueOf(seckill.getSkuId()), regularSeckill);
                        redisService.setExpire(key, 2L, TimeUnit.DAYS);
                    }
                    // 保存一个标识，防止分布式重复上架
                    redisService.setObject(SECKILL_MARK_KEY + targetFormatter.format(localDate), SECKILL_MARK_VALUE);
                }
            }catch (Exception e){
                redisService.deleteObject(SECKILL_MARK_KEY + targetFormatter.format(localDate));
            } finally {
                lock.unlock();
            }
        }
    }

    @Override
    public List<RegularSeckill> getNowSeckillSku(Long promotionId) {
        LocalDate now = LocalDate.now();
        String nowDate = targetFormatter.format(now);
        String redisKey = SeckillRedisConstants.REGULAR_SECKILL + nowDate + ":" + promotionId + ":*";
        Set<String> keys = redisService.getKey(redisKey);
        LocalTime nowTime = LocalTime.now();
        ArrayList<RegularSeckill> arrayList = new ArrayList<>();
        for (String key : keys) {
            String replace = key.replace(SeckillRedisConstants.REGULAR_SECKILL + nowDate + ":" + promotionId + ":", "");
            String[] split = replace.split("_");
            if (LocalTime.parse(split[0]).isBefore(nowTime) && LocalTime.parse(split[1]).isAfter(nowTime)){
                Map<String, RegularSeckill> hash = redisService.getHash(key);
                for (Map.Entry<String, RegularSeckill> entry : hash.entrySet()) {
                    RegularSeckill mapValue = entry.getValue();
                    RegularSeckill seckill = new RegularSeckill();
                    BeanUtils.copyProperties(mapValue, seckill);
                    arrayList.add(seckill);
                }
            }
        }
        return arrayList;
    }

    @Override
    public RegularSeckill getSeckillSku(Long skuId) {
        LocalDate now = LocalDate.now();
        String nowDate = targetFormatter.format(now);
        String redisKey = SeckillRedisConstants.REGULAR_SECKILL + nowDate + ":*";
        Set<String> keys = redisService.getKey(redisKey);
        RegularSeckill res = null;
        for (String key : keys) {
            RegularSeckill regularSeckill =  redisService.getHashValue(key, String.valueOf(skuId));
            if (regularSeckill == null){
                continue;
            }
            if (res == null){
                res = regularSeckill;
                continue;
            }
            if (res.getStartTime().isAfter(regularSeckill.getStartTime())){
                res = regularSeckill;
            }
        }
        if (res != null && res.getStartTime().isAfter(LocalTime.now())){
            res.setRandomCode(null);
        }else if (res != null){
            res.setSeckillCount(redisService.getObject(SECKILL_SKU_SEMAPHORE + res.getRandomCode()));
        }
        return res;
    }
}
